<template>
  <div id="root">
    <div class="container">
      <div class="graph" @dragover="handleDragOver" @drop="handleDrop">
        <grids />
        <selection v-if="isSelecting" />
        <div class="nodes" :style="nodesStyle">
          <node v-for="(node, index) in nodes" :key="node.id" v-bind="node" :nodeIndex="index" />
        </div>
        <svg v-if="isLinking || (isMenuShow && menuType === 'add-node-menu')" class="links">
          <pin-link v-for="(link, index) in linkingPins" :key="index" :src="link" :dst="null" />
        </svg>
        <svg class="links">
          <pin-link
            v-for="(link, index) in links"
            :key="index"
            :src="{ nodeId: link.srcNodeId, pinTitle: link.srcPinTitle, isInput: false }"
            :dst="{ nodeId: link.dstNodeId, pinTitle: link.dstPinTitle, isInput: true }"
          />
        </svg>
        <context-menu v-if="isMenuShow" :pos="menuPos">
          <component :is="menuType" />
        </context-menu>
      </div>
      <side-panel />
    </div>
  </div>
</template>

<script>
import Grids from './Grids.vue';
import Selection from './Selection.vue';
import Node from './Node.vue';
import PinLink from './PinLink.vue';
import ContextMenu from './ContextMenu.vue';
import AddNodeMenu from './AddNodeMenu.vue';
import AddVariableMenu from './AddVariableMenu.vue';
import SidePanel from './SidePanel.vue';
import { mapState, mapMutations, mapGetters } from 'vuex';

export default {
  components: { Grids, Selection, Node, PinLink, ContextMenu, AddNodeMenu, AddVariableMenu, SidePanel },

  data() {
    return {
      mousePos: { x: 0, y: 0 },
    };
  },

  created() {
    window.addEventListener('message', this.handleMessage);
    document.body.addEventListener('mouseup', this.handleMouseUp);
    document.body.addEventListener('mousemove', this.handleMouseMove);
    document.body.addEventListener('mouseleave', this.handleMouseLeave);
    document.body.addEventListener('mousewheel', this.handleMouseWheel);
    document.body.addEventListener('keydown', this.handleKeyDown);
  },

  destroyed() {
    window.removeEventListener('message', this.handleMessage);
    document.body.removeEventListener('mouseup', this.handleMouseUp);
    document.body.removeEventListener('mousemove', this.handleMouseMove);
    document.body.removeEventListener('mouseleave', this.handleMouseLeave);
    document.body.removeEventListener('mousewheel', this.handleMouseWheel);
    document.body.removeEventListener('keydown', this.handleKeyDown);
  },

  methods: {
    ...mapMutations([
      'dragEnd',
      'drag',
      'setScale',
      'updateCategories',
      'updateGraph',
      'addVariableMenu',
      'deleteNodes',
      'copyNodes',
      'cutNodes',
      'pasteNodes',
    ]),

    handleMessage(ev) {
      const msg = ev.data;
      switch (msg.type) {
        case 'UPDATE_CATEGORIES':
          this.updateCategories(msg.categories);
          break;

        case 'UPDATE_GRAPH':
          this.updateGraph(msg.text);
          break;
      }
    },

    handleMouseUp(ev) {
      if (this.isDragging) {
        this.dragEnd({ x: ev.clientX, y: ev.clientY });
      }
    },

    handleMouseMove(ev) {
      this.mousePos = { x: ev.clientX, y: ev.clientY };
      if (this.isDragging) {
        this.drag({ x: ev.clientX, y: ev.clientY });
      }
    },

    handleMouseWheel(ev) {
      this.setScale(ev.deltaY);
    },

    handleMouseLeave() {
      if (this.isDragging) {
        this.dragEnd();
      }
    },

    handleKeyDown(ev) {
      if (ev.key === 'Delete' || ev.key === 'Backspace') {
        this.deleteNodes();
      } else if (ev.key === 'c' && (ev.ctrlKey || ev.metaKey)) {
        this.copyNodes();
      } else if (ev.key === 'x' && (ev.ctrlKey || ev.metaKey)) {
        this.cutNodes();
      } else if (ev.key === 'v' && (ev.ctrlKey || ev.metaKey)) {
        this.pasteNodes(this.mousePos);
      }
    },

    handleDragOver(ev) {
      ev.preventDefault();
    },

    handleDrop(ev) {
      ev.preventDefault();
      const index = parseInt(ev.dataTransfer.getData('text/plain'));
      this.addVariableMenu({ x: ev.clientX, y: ev.clientY, index });
    },
  },

  computed: {
    ...mapState([
      'origin',
      'scale',
      'isDragging',
      'dragEndPos',
      'nodes',
      'links',
      'linkingPins',
      'isMenuShow',
      'menuPos',
      'menuType',
    ]),
    ...mapGetters(['isSelecting', 'isLinking']),

    nodesStyle() {
      return {
        transform: `scale(${this.scale})`,
        left: `${this.origin.x}px`,
        top: `${this.origin.y}px`,
      };
    },
  },
};
</script>

<style>
html,
body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
}

#root {
  width: 100%;
  height: 100%;
  background-color: var(--vscode-editor-background);
}

.container {
  width: 100%;
  height: 100%;
  display: flex;
}

.graph {
  position: relative;
  overflow: hidden;
  -webkit-user-select: none;
  user-select: none;
}

.nodes {
  position: absolute;
}

.links {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

input[type='text'],
input[type='number'] {
  background-color: var(--vscode-input-background);
  color: var(--vscode-input-foreground);
  border: thin solid var(--vscode-input-border);
  box-sizing: border-box;
  height: 1.1em;
}

input[type='text']:focus,
input[type='number']:focus {
  box-sizing: border-box;
  height: 1.1em;
  outline: none !important;
  border: thin solid var(--vscode-focusBorder);
}

input[type='number']:invalid {
  background-color: var(--vscode-inputValidation-errorBackground);
  color: var(--vscode-inputValidation-errorForeground);
  border: thin solid var(--vscode-inputValidation-errorBorder);
}

input[type='number']::-webkit-inner-spin-button {
  -webkit-appearance: none;
}

input[type='checkbox'] {
  -webkit-appearance: none;
  outline: none !important;
  cursor: pointer;
  width: 1em;
  height: 1em;
  background-color: var(--vscode-settings-checkboxBackground);
  color: var(--vscode-settings-checkboxForeground);
  border: thin solid var(--vscode-settings-checkboxBorder);
  content: '\00a0';
  line-height: 1em;
}

input[type='checkbox']:checked::before {
  content: '\2713';
  text-align: center;
}
</style>
